import { VantComponent } from '../common/component'
import { isDef } from '../common/validator'
import { pickerProps } from '../picker/shared'
const currentYear = new Date().getFullYear()
function isValidDate(date) {
  return isDef(date) && !isNaN(new Date(date).getTime())
}
function range(num, min, max) {
  return Math.min(Math.max(num, min), max)
}
function padZero(val) {
  return `00${val}`.slice(-2)
}
function times(n, iteratee) {
  let index = -1
  const result = Array(n < 0 ? 0 : n)
  while (++index < n) {
    result[index] = iteratee(index)
  }
  return result
}
function getTrueValue(formattedValue) {
  if (formattedValue === undefined) {
    formattedValue = '1'
  }
  while (isNaN(parseInt(formattedValue, 10))) {
    formattedValue = formattedValue.slice(1)
  }
  return parseInt(formattedValue, 10)
}
function getMonthEndDay(year, month) {
  return 32 - new Date(year, month - 1, 32).getDate()
}
const defaultFormatter = (type, value) => value
VantComponent({
  classes: ['active-class', 'toolbar-class', 'column-class'],
  props: Object.assign(Object.assign({}, pickerProps), {
    value: {
      type: null,
      observer: 'updateValue',
    },
    filter: null,
    type: {
      type: String,
      value: 'datetime',
      observer: 'updateValue',
    },
    showToolbar: {
      type: Boolean,
      value: true,
    },
    formatter: {
      type: null,
      value: defaultFormatter,
    },
    minDate: {
      type: Number,
      value: new Date(currentYear - 10, 0, 1).getTime(),
      observer: 'updateValue',
    },
    maxDate: {
      type: Number,
      value: new Date(currentYear + 10, 11, 31).getTime(),
      observer: 'updateValue',
    },
    minHour: {
      type: Number,
      value: 0,
      observer: 'updateValue',
    },
    maxHour: {
      type: Number,
      value: 23,
      observer: 'updateValue',
    },
    minMinute: {
      type: Number,
      value: 0,
      observer: 'updateValue',
    },
    maxMinute: {
      type: Number,
      value: 59,
      observer: 'updateValue',
    },
  }),
  data: {
    innerValue: Date.now(),
    columns: [],
  },
  methods: {
    updateValue() {
      const { data } = this
      const val = this.correctValue(data.value)
      const isEqual = val === data.innerValue
      this.updateColumnValue(val).then(() => {
        if (!isEqual) {
          this.$emit('input', val)
        }
      })
    },
    getPicker() {
      if (this.picker == null) {
        this.picker = this.selectComponent('.van-datetime-picker')
        const { picker } = this
        const { setColumnValues } = picker
        picker.setColumnValues = (...args) => setColumnValues.apply(picker, [...args, false])
      }
      return this.picker
    },
    updateColumns() {
      const { formatter = defaultFormatter } = this.data
      const results = this.getOriginColumns().map((column) => ({
        values: column.values.map((value) => formatter(column.type, value)),
      }))
      return this.set({ columns: results })
    },
    getOriginColumns() {
      const { filter } = this.data
      const results = this.getRanges().map(({ type, range }) => {
        let values = times(range[1] - range[0] + 1, (index) => {
          const value = range[0] + index
          return type === 'year' ? `${value}` : padZero(value)
        })
        if (filter) {
          values = filter(type, values)
        }
        return { type, values }
      })
      return results
    },
    getRanges() {
      const { data } = this
      if (data.type === 'time') {
        return [
          {
            type: 'hour',
            range: [data.minHour, data.maxHour],
          },
          {
            type: 'minute',
            range: [data.minMinute, data.maxMinute],
          },
        ]
      }
      const { maxYear, maxDate, maxMonth, maxHour, maxMinute } = this.getBoundary(
        'max',
        data.innerValue,
      )
      const { minYear, minDate, minMonth, minHour, minMinute } = this.getBoundary(
        'min',
        data.innerValue,
      )
      const result = [
        {
          type: 'year',
          range: [minYear, maxYear],
        },
        {
          type: 'month',
          range: [minMonth, maxMonth],
        },
        {
          type: 'day',
          range: [minDate, maxDate],
        },
        {
          type: 'hour',
          range: [minHour, maxHour],
        },
        {
          type: 'minute',
          range: [minMinute, maxMinute],
        },
      ]
      if (data.type === 'date') result.splice(3, 2)
      if (data.type === 'year-month') result.splice(2, 3)
      return result
    },
    correctValue(value) {
      const { data } = this
      // validate value
      const isDateType = data.type !== 'time'
      if (isDateType && !isValidDate(value)) {
        value = data.minDate
      } else if (!isDateType && !value) {
        const { minHour } = data
        value = `${padZero(minHour)}:00`
      }
      // time type
      if (!isDateType) {
        let [hour, minute] = value.split(':')
        hour = padZero(range(hour, data.minHour, data.maxHour))
        minute = padZero(range(minute, data.minMinute, data.maxMinute))
        return `${hour}:${minute}`
      }
      // date type
      value = Math.max(value, data.minDate)
      value = Math.min(value, data.maxDate)
      return value
    },
    getBoundary(type, innerValue) {
      const value = new Date(innerValue)
      const boundary = new Date(this.data[`${type}Date`])
      const year = boundary.getFullYear()
      let month = 1
      let date = 1
      let hour = 0
      let minute = 0
      if (type === 'max') {
        month = 12
        date = getMonthEndDay(value.getFullYear(), value.getMonth() + 1)
        hour = 23
        minute = 59
      }
      if (value.getFullYear() === year) {
        month = boundary.getMonth() + 1
        if (value.getMonth() + 1 === month) {
          date = boundary.getDate()
          if (value.getDate() === date) {
            hour = boundary.getHours()
            if (value.getHours() === hour) {
              minute = boundary.getMinutes()
            }
          }
        }
      }
      return {
        [`${type}Year`]: year,
        [`${type}Month`]: month,
        [`${type}Date`]: date,
        [`${type}Hour`]: hour,
        [`${type}Minute`]: minute,
      }
    },
    onCancel() {
      this.$emit('cancel')
    },
    onConfirm() {
      this.$emit('confirm', this.data.innerValue)
    },
    onChange() {
      const { data } = this
      let value
      const picker = this.getPicker()
      const originColumns = this.getOriginColumns()
      if (data.type === 'time') {
        const indexes = picker.getIndexes()
        value = `${+originColumns[0].values[indexes[0]]}:${+originColumns[1].values[indexes[1]]}`
      } else {
        const indexes = picker.getIndexes()
        const values = indexes.map((value, index) => originColumns[index].values[value])
        const year = getTrueValue(values[0])
        const month = getTrueValue(values[1])
        const maxDate = getMonthEndDay(year, month)
        let date = getTrueValue(values[2])
        if (data.type === 'year-month') {
          date = 1
        }
        date = date > maxDate ? maxDate : date
        let hour = 0
        let minute = 0
        if (data.type === 'datetime') {
          hour = getTrueValue(values[3])
          minute = getTrueValue(values[4])
        }
        value = new Date(year, month - 1, date, hour, minute)
      }
      value = this.correctValue(value)
      this.updateColumnValue(value).then(() => {
        this.$emit('input', value)
        this.$emit('change', picker)
      })
    },
    updateColumnValue(value) {
      let values = []
      const { type } = this.data
      const formatter = this.data.formatter || defaultFormatter
      const picker = this.getPicker()
      if (type === 'time') {
        const pair = value.split(':')
        values = [formatter('hour', pair[0]), formatter('minute', pair[1])]
      } else {
        const date = new Date(value)
        values = [
          formatter('year', `${date.getFullYear()}`),
          formatter('month', padZero(date.getMonth() + 1)),
        ]
        if (type === 'date') {
          values.push(formatter('day', padZero(date.getDate())))
        }
        if (type === 'datetime') {
          values.push(
            formatter('day', padZero(date.getDate())),
            formatter('hour', padZero(date.getHours())),
            formatter('minute', padZero(date.getMinutes())),
          )
        }
      }
      return this.set({ innerValue: value })
        .then(() => this.updateColumns())
        .then(() => picker.setValues(values))
    },
  },
  created() {
    const innerValue = this.correctValue(this.data.value)
    this.updateColumnValue(innerValue).then(() => {
      this.$emit('input', innerValue)
    })
  },
})
